package com.hqd.jjvm.jnative.lang;

import com.hqd.jjvm.jnative.JNativeRegistry;
import com.hqd.jjvm.rtda.JThread;
import com.hqd.jjvm.rtda.LocalVars;
import com.hqd.jjvm.rtda.StackFrame;
import com.hqd.jjvm.rtda.heap.JClass;
import com.hqd.jjvm.rtda.heap.JField;
import com.hqd.jjvm.rtda.heap.JMethod;
import com.hqd.jjvm.rtda.obj.JObject;
import com.hqd.jjvm.rtda.obj.JString;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class JSystemNativeRegistry extends JNativeRegistry {

    private static final JSystemNativeRegistry instance = new JSystemNativeRegistry();
    protected static final String CLASS_PATH = "java/lang/System";

    static {
        registry(CLASS_PATH, "registerNatives", "()V", JNativeRegistry::registerNatives);
        registry(CLASS_PATH, "initProperties", "(Ljava/util/Properties;)Ljava/util/Properties;", JSystemNativeRegistry::initProperties);
        registry(CLASS_PATH, "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", JSystemNativeRegistry::arraycopy);
        registry(CLASS_PATH, "setIn0", "(Ljava/io/InputStream;)V", JSystemNativeRegistry::setIn0);
        registry(CLASS_PATH, "setOut0", "(Ljava/io/PrintStream;)V", JSystemNativeRegistry::setOut0);
        registry(CLASS_PATH, "setErr0", "(Ljava/io/PrintStream;)V", JSystemNativeRegistry::setErr0);
        registry(CLASS_PATH, "currentTimeMillis", "()J", JSystemNativeRegistry::currentTimeMillis);
        registry(CLASS_PATH, "nanoTime", "()J", JSystemNativeRegistry::nanoTime);
        registry(CLASS_PATH, "identityHashCode", "(Ljava/lang/Object;)I", JSystemNativeRegistry::identityHashCode);
        registry(CLASS_PATH, "mapLibraryName", "(Ljava/lang/String;)Ljava/lang/String;", JSystemNativeRegistry::mapLibraryName);

    }

    private static void mapLibraryName(JThread jThread) {
        JObject ref = jThread.getJvmStack().getTop().getLocalVars().getRefVal(0);
        jThread.getJvmStack().pushOperandStackRefVal(JString.newJString(ref.getJClass().getLoader(), null));
    }

    protected static void identityHashCode(JThread jThread) {
        LocalVars localVars = jThread.getJvmStack().getTop().getLocalVars();
        JObject ref = localVars.getRefVal(0);
        int code = System.identityHashCode(ref.getData());
        jThread.getJvmStack().pushOperandStackIntVal(code);
    }

    protected static void nanoTime(JThread jThread) {
        jThread.getJvmStack().pushOperandStackLongVal(System.nanoTime());
    }

    protected static void currentTimeMillis(JThread jThread) {
        jThread.getJvmStack().pushOperandStackLongVal(System.currentTimeMillis());
    }

    protected static void setErr0(JThread jThread) {
        JObject err = jThread.getJvmStack().getLocalVarsRefVal(0);
        JClass jc = jThread.getJvmStack().getTop().getJClass();
        JField jf = jc.getJField("err", "Ljava/io/PrintStream;");
        jc.getJObject().getField(jf.getSlotId()).setVal(err);
        jc.getStaticVars()[jf.getSlotId()].setVal(err);
    }

    protected static void setOut0(JThread jThread) {
        JObject out = jThread.getJvmStack().getLocalVarsRefVal(0);
        JClass jc = jThread.getJvmStack().getTop().getJClass();
        JField jf = jc.getJField("out", "Ljava/io/PrintStream;");
        jc.getJObject().getField(jf.getSlotId()).setVal(out);
        jc.getStaticVars()[jf.getSlotId()].setVal(out);
    }

    protected static void setIn0(JThread jThread) {
        JObject in = jThread.getJvmStack().getLocalVarsRefVal(0);
        JClass jc = jThread.getJvmStack().getTop().getJClass();
        JField jf = jc.getJField("in", "Ljava/io/InputStream;");
        in.getField(jf.getSlotId()).setVal(in);
        jc.getStaticVars()[jf.getSlotId()].setVal(in);
    }

    protected static void initProperties(JThread jThread) {
        StackFrame stackFrame = jThread.getJvmStack().getTop();
        JObject ref = stackFrame.getLocalVars().getRefVal(0);
        jThread.getJvmStack().getTop().pushRef(ref);
        JMethod setPropMethod = ref.getJClass().getJMethod("setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;");
        // Properties properties = System.getProperties();
        //hack
        Map<String, String> properties = new HashMap<>();
        properties.put("java.version", "1.8");
        properties.put("java.vendor", "jjvm");
        properties.put("file.encoding", "UTF-8");
        properties.put("sun.stdout.encoding", "UTF-8");
        properties.put("sun.stderr.encoding", "UTF-8");
        properties.put("file.separator", "/");
        properties.put("path.separator", ":");
        properties.put("line.separator", "\n");
        properties.put("user.home", "");
        properties.put("user.dir", ".");
        try {
            JClass jClass = setPropMethod.getJClass().getLoader().loadJClass("java/lang/String");
            for (Object key : properties.keySet()) {
                StackFrame newFrame = new StackFrame(setPropMethod);
                newFrame.getLocalVars().setVal(0, ref);

                JString keyStr = jClass.getConstantPool().getStringPool().internString(jClass.getLoader(), key.toString());
                newFrame.getLocalVars().setVal(1, keyStr);

                JString valStr = jClass.getConstantPool().getStringPool().internString(jClass.getLoader(), properties.get(key).toString());
                newFrame.getLocalVars().setVal(2, valStr);

                jThread.pushFrame(newFrame);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    protected static void arraycopy(JThread jThread) {
        LocalVars localVars = jThread.getJvmStack().getTop().getLocalVars();
        JObject src = localVars.getRefVal(0);
        int srcPos = localVars.getIntVal(1);
        JObject dest = localVars.getRefVal(2);
        int destPos = localVars.getIntVal(3);
        int length = localVars.getIntVal(4);
        System.arraycopy(src.getData(), srcPos, dest.getData(), destPos, length);

    }

    public static JSystemNativeRegistry getInstance() {
        return instance;
    }
}
